using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Xml;
using Common.Utilities;
using Common.Utilities.Dispatcher;
using Common.Utilities.DLR;
using Workflows.Components.DALC;

namespace Workflows.Components
{
    /// <summary></summary>
    public class SubflowWorker:IWorker
    {
        private Thread _WorkerThread;
        private SubflowInstruction _Instruction;
        private SubflowOutput _Output;
        private Guid _TicketID;
        private RunContext _Context;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }

        private Dictionary<Type, Type> _ComponentWorkerType;    // componentType->workertype
        private Dictionary<string, Type> _InstructionTypes;      // map typefullname to instruction type
        private Dictionary<string, Type> _OutputTypes;            // map typefullname to output type
        private WorkflowEngine _Engine;

        /// <summary></summary>
        public Thread WorkerThread
        {
            get { return _WorkerThread; }
        }

        /// <summary></summary>
        public InstructionBase Instruction
        {
            get { return _Instruction; }
        }

        /// <summary></summary>
        public ResultBase Output
        {
            get { return this._Output; }
        }

        /// <summary></summary>
        public Type ComponentType
        {
            get { return typeof(Subflow); }
        }

        /// <summary></summary>
        public RunContext Context
        {
            get { return this._Context; }
            set { this._Context = value; }
        }

        /// <summary></summary>
        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._TicketID = ticketGuid;
            this._Instruction = (SubflowInstruction) request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        /// <summary></summary>
        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._Context = context;
            this._TicketID = ticketGuid;
            this._Instruction = (SubflowInstruction) request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        /// <summary></summary>
        public void DoWork()
        {
            if(this.WorkerStartedEvent !=null)
            {
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this._Instruction);
            }
            this._WorkerThread = Thread.CurrentThread;
            this.Iterate();
        }

        /// <summary></summary>
        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this._Instruction);
            }
            this._WorkerThread = new Thread(new ThreadStart(this.Iterate));
            this._WorkerThread.Start();
        }

        /// <summary></summary>
        public void CancelWork()
        {
            if(this._WorkerThread !=null && this._WorkerThread.IsAlive)
            {
                this._WorkerThread.Abort();
                this._WorkerThread.Join();

                if(this.WorkerCancelledEvent !=null)
                {
                    this.WorkerCancelledEvent(this._TicketID, this.GetType(), this._Instruction);
                }
            }
        }

        public event WorkerStarted WorkerStartedEvent;
        public event WorkerFinished WorkerFinishedEvent;
        public event WorkerHasError WorkerErredEvent;
        public event WorkerCancelled WorkerCancelledEvent;
        public event WorkerStatusChanged WorkerStatusChangedEvent;

        #region work
        private void RegisterTypes()
        {
            this._InstructionTypes = new Dictionary<string, Type>();
            this._OutputTypes = new Dictionary<string, Type>();
            this._ComponentWorkerType = new Dictionary<Type, Type>();

            string dllFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            DirectoryInfo binFolderInfno = new DirectoryInfo(dllFolderPath);
            FileInfo[] libInfos = binFolderInfno.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
            foreach (FileInfo libFileInfo in libInfos)
            {
                try
                {
                    Assembly libAssembly = Assembly.LoadFile(libFileInfo.FullName);
                    Type[] libTypes = libAssembly.GetTypes();
                    foreach (Type type in libTypes)
                    {
                        if (type.GetInterface(typeof(IWorker).FullName) != null && (!type.IsAbstract))
                        {
                            IWorker worker = (IWorker) Activator.CreateInstance(type);
                            this._ComponentWorkerType.Add(worker.ComponentType, type);
                        }
                        if (type.IsSubclassOf(typeof(InstructionBase)) &&
                            (!this._InstructionTypes.ContainsKey(type.FullName)))
                        {
                            this._InstructionTypes.Add(type.FullName, type);
                        }
                        if (type.IsSubclassOf(typeof(ResultBase)) && (!this._OutputTypes.ContainsKey(type.FullName)))
                        {
                            this._OutputTypes.Add(type.FullName, type);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Unable to load assembly: " + ex.ToString());
                }
            }
        }

        private void Iterate()
        {
            int iterationCount = 0;
            int currentIndex = 0;
            this._Engine=new WorkflowEngine();
            this.RegisterTypes();

            try
            {
                WorkflowDesign wfDesign = WorkflowDesignDALC.GetWorkflowDesign(this._Instruction.SubFlowID);
                if (this._Instruction.LoopableData == null || this._Instruction.LoopableData.Count == 0)
                    throw new Exception("Subflow has no iteration to continue");
                List<GlobalVariable> gVars = new List<GlobalVariable>();
                if (wfDesign.GlobalVariables != null && wfDesign.GlobalVariables.Length > 0)
                {
                    gVars.AddRange(wfDesign.GlobalVariables);
                }
                RunContext context = new RunContext(this._TicketID, true, gVars);

                for (int i = 0; i < this._Instruction.LoopableData.Count; i++)
                {
                    iterationCount++;
                    currentIndex = i;
                    context = this.UpdateInitialRunContext(context, this._Instruction.LoopableData[i]);

                    List<WorkflowComponentBase> wfComponents = new List<WorkflowComponentBase>();
                    if (wfDesign.Components != null && wfDesign.Components.Length > 0)
                    {
                        foreach (WorkflowComponentBase wfComponent in wfDesign.Components)
                        {
                            if (!wfComponent.GetType().IsSubclassOf(typeof(WorkflowTriggerBase)))
                            {
                                wfComponents.Add(wfComponent);
                            }
                        }
                    }
                    List<Connector> wfConnectors = new List<Connector>();
                    if (wfDesign.Connectors != null && wfDesign.Connectors.Length > 0)
                    {
                        foreach (Connector connector in wfDesign.Connectors)
                        {
                            wfConnectors.Add(connector);
                        }
                    }
                    Dictionary<Guid, ResultBase> componentOutputs =new Dictionary<Guid, ResultBase>();
                    

                    while (componentOutputs.Count < wfComponents.Count)
                    {
                        List<WorkflowComponentBase> wfComponentsToRun =
                            this.PickNextWorkflowComponents(this._Engine, wfComponents, componentOutputs, wfConnectors);
                        if (wfComponentsToRun == null || wfComponentsToRun.Count == 0)
                            break;

                        foreach(WorkflowComponentBase wfComponent in wfComponentsToRun)
                        {
                            InstructionBase instruction = wfComponent.Instruction;
                            List<Guid> dependencies =
                                this._Engine.GetComponentDependencies(wfComponent.ComponentID, wfConnectors, wfComponents);
                            if(dependencies==null || dependencies.Count ==0)
                            {
                                Type workerType = this._ComponentWorkerType[wfComponent.GetType()];
                                IWorker workerInstance = (IWorker) Activator.CreateInstance(workerType);


                                workerInstance.SetInstruction(context, Guid.NewGuid(), instruction, this.WorkflowName,
                                                              this.ComponentName);
                                workerInstance.DoWork();
                                context = workerInstance.Context;
                                componentOutputs.Add(wfComponent.ComponentID, workerInstance.Output);
                            }
                            else
                            {
                                List<ResultBase> upstreamOutputs=new List<ResultBase>();
                                foreach(Guid dependency in dependencies)
                                {
                                    if(componentOutputs.ContainsKey(dependency))
                                    {
                                        upstreamOutputs.Add(componentOutputs[dependency]);
                                    }
                                }
                                Type workerType = this._ComponentWorkerType[wfComponent.GetType()];
                                IWorker workerInstance = (IWorker) Activator.CreateInstance(workerType);
                                instruction =
                                    this.ComputeJobInstruction(wfDesign, instruction, upstreamOutputs);
                                workerInstance.SetInstruction(context, Guid.NewGuid(), instruction, this.WorkflowName, this.ComponentName);
                                workerInstance.DoWork();
                                context = workerInstance.Context;
                                componentOutputs.Add(wfComponent.ComponentID, workerInstance.Output);
                            }
                        }
                    }
                    
                }

                this._Output =
                        new SubflowOutput(this._Instruction.OwnerComponentID, this._TicketID,
                                          currentIndex, iterationCount, context.Vars);
                    
                if (this.WorkerFinishedEvent != null)
                {
                    this.WorkerFinishedEvent(this._TicketID, this.GetType(), this._Instruction, this._Output);
                }
            }
            catch (Exception ex)
            {
                if (this.WorkerErredEvent != null)
                {
                    this.WorkerErredEvent(this._TicketID, this.GetType(), this._Instruction,
                                          string.Format("Iteration {0}: {1}", iterationCount, ex.ToString()),true);
                }
            }
        }

        private List<WorkflowComponentBase> PickNextWorkflowComponents(WorkflowEngine wfEngine, List<WorkflowComponentBase> allWfComponents, Dictionary<Guid, ResultBase> finishedWfComponentOutputs, List<Connector> allConnectors)
        {
            List<WorkflowComponentBase> workflowComponentsThatAreReadyToRun=new List<WorkflowComponentBase>();
            foreach(WorkflowComponentBase wfComponent in allWfComponents)
            {
                if(!finishedWfComponentOutputs.ContainsKey(wfComponent.ComponentID))
                {
                    List<Guid> dependencies = wfEngine.GetComponentDependencies(wfComponent.ComponentID, allConnectors, allWfComponents);
                    if(dependencies==null || dependencies.Count==0)
                    {
                        workflowComponentsThatAreReadyToRun.Add(wfComponent);
                    }
                    else
                    {
                        int upstreamWorkFinishedCount = 0;
                        foreach(Guid dependency in dependencies)
                        {
                            if(finishedWfComponentOutputs.ContainsKey(dependency))
                            {
                                upstreamWorkFinishedCount++;
                            }
                        }
                        if(upstreamWorkFinishedCount==dependencies.Count)
                        {
                            workflowComponentsThatAreReadyToRun.Add(wfComponent);
                        }
                    }
                }
            }
            return workflowComponentsThatAreReadyToRun;
        }

        private InstructionBase ComputeJobInstruction(WorkflowDesign wfDesign, InstructionBase instruction,List<ResultBase> upstreamOutputs)
        {
            if(instruction.GetType()==typeof(GlobalVariableSetterInstruction))
            {
                GlobalVariableSetterInstruction setterInstruction = (GlobalVariableSetterInstruction) instruction;
                if(setterInstruction.Setters!=null && setterInstruction.Setters.Count>0)
                {
                    for (int i = 0; i < setterInstruction.Setters.Count; i++)
                    {
                        FieldSetter setter = setterInstruction.Setters[i];
                        foreach(ResultBase upstreamOutput in upstreamOutputs)
                        {
                            if(upstreamOutput.OwnerComponentID==setter.SrcComponentID)
                            {
                                Dictionary<string, Type> outputMembers =
                                    upstreamOutput.GetBindableMemberNames(new Type[] {typeof (ResultBase)});
                                foreach(string srcFieldName in outputMembers.Keys)
                                {
                                    if(srcFieldName==setter.SrcFieldName)
                                    {
                                        object srcFieldValue = upstreamOutput.ReadMemberValue(srcFieldName);
                                        if(!string.IsNullOrEmpty(setter.TransformationCodeSnippet))
                                        {
                                            List<MethodArgument> args = new List<MethodArgument>();
                                            MethodArgument arg =
                                                new MethodArgument(
                                                    "fromObj", srcFieldValue.GetType(), false, srcFieldValue);
                                            args.Add(arg);
                                            MethodConstructInfo methodBuilder =
                                                new MethodConstructInfo(
                                                    SimpleDataTypeUtil.ToSystemType(this._Context.Vars[i].DataType),
                                                    "Convert",
                                                    args, setter.TransformationCodeSnippet);
                                            srcFieldValue = CodeDomUtil.ExecuteMethod(methodBuilder);
                                        }
                                        setter.SrcFieldValue = srcFieldValue;
                                        setterInstruction.Setters[i] = setter;
                                        break;
                                    }
                                }

                                break;
                            }
                        }
                    }
                    instruction = setterInstruction;
                }
            }

            Dictionary<string, MemberInfo> writableMembers = TypeUtil.GetWritableMembers(instruction.GetType());
            if(upstreamOutputs !=null && upstreamOutputs.Count>0)
            {
                foreach(ResultBase jobOutput in upstreamOutputs)
                {
                    if (jobOutput != null)
                    {
                        Dictionary<string, Type> outputMembers =
                            jobOutput.GetBindableMemberNames(new Type[] {typeof (ResultBase)});
                        Connector wfConnector = wfDesign.GetConnector(jobOutput.OwnerComponentID, instruction.OwnerComponentID);
                        if (outputMembers !=null && wfConnector!=null && wfConnector.FieldMappers != null)
                        {
                            foreach (CodeFieldMapper fieldMapper in wfConnector.FieldMappers)
                            {
                                if (outputMembers.ContainsKey(fieldMapper.SrcFieldName) &&
                                    writableMembers.ContainsKey(fieldMapper.TgtFieldName) &&
                                    fieldMapper.SrcComponentID==wfConnector.FromComponentID &&
                                    fieldMapper.TgtComponentID == wfConnector.ToComponentID)
                                {
                                    MemberInfo outboundField =
                                        writableMembers[fieldMapper.TgtFieldName];
                                    object inboundFieldValue = jobOutput.ReadMemberValue(fieldMapper.SrcFieldName);

                                    if (outboundField.MemberType == MemberTypes.Field)
                                    {
                                        if (inboundFieldValue.GetType() ==
                                            ((FieldInfo) outboundField).FieldType)
                                        {
                                            ((FieldInfo) outboundField).SetValue(instruction,
                                                                                 inboundFieldValue);
                                        }
                                        else
                                        {
                                            object tmp =
                                                Convert.ChangeType(inboundFieldValue.ToString(),
                                                                   ((FieldInfo) outboundField).
                                                                       FieldType);
                                            ((FieldInfo) outboundField).SetValue(instruction, tmp);
                                        }
                                    }
                                    else
                                    {
                                        if (inboundFieldValue.GetType() ==
                                            ((PropertyInfo) outboundField).PropertyType)
                                        {
                                            ((PropertyInfo) outboundField).SetValue(instruction,
                                                                                    inboundFieldValue,
                                                                                    null);
                                        }
                                        else
                                        {
                                            object tmp =
                                                Convert.ChangeType(inboundFieldValue.ToString(),
                                                                   ((PropertyInfo) outboundField).
                                                                       PropertyType);
                                            ((PropertyInfo) outboundField).SetValue(instruction, tmp,
                                                                                    null);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return instruction;
        }

        private RunContext UpdateInitialRunContext(RunContext context, string currentIterationValue)
        {
            if(context !=null && context.Vars !=null && context.Vars.Count>0)
            {
                for(int i=0;i<context.Vars.Count;i++)
                {
                    if(context.Vars[i].VarName==this._Instruction.IteratorDataName)
                    {
                        context.Vars[i].VarValue = currentIterationValue;
                        break;
                    }
                }
            }
            return context;
        }
        #endregion
    }
}
